/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.constants.CodeConstants;
import org.zhiqim.kernel.httpclient.HttpGetStream;
import org.zhiqim.kernel.httpclient.HttpResult;
import org.zhiqim.kernel.model.maps.MapSO;
import org.zhiqim.kernel.model.maps.MapSS;
import org.zhiqim.kernel.model.maps.TreeMapSS;

/**
 * URL相关工具类
 *
 * @version v1.0.0 @author zouzhigang 2014-2-27 新建与整理
 */
@AnAlias("Urls")
public class Urls implements CodeConstants
{
    /** URL 按UTF-8编码 */
    public static String encodeUTF8(String src)
    {
        return encode(src, _UTF_8_);
    }
    
    /** URL编码 */
    public static String encode(String src, String encoding)
    {
        if (Validates.isEmpty(src))
            return src;

        try{return URLEncoder.encode(src, encoding);}
        catch (UnsupportedEncodingException e){throw Asserts.exception("非法的字符编码["+encoding+"]");}
    }
    
    /** URL 按UTF-8解码 */
    public static String decodeUTF8(String src)
    {
        return decode(src, _UTF_8_);
    }
    
    /** URL解码 */
    public static String decode(String src, String encoding)
    {
        if (Validates.isEmpty(src))
            return src;

        try{return URLDecoder.decode(src, encoding);}
        catch (UnsupportedEncodingException e){throw Asserts.exception("非法的字符编码["+encoding+"]");}
    }
    
    /**
     * 根据URL，默认以UTF-8生成KEY和VALUE放置到指定的哈希表
     * 
     * @param url URL
     * @param map 指定哈希表
     */
    public static void toMapUTF8(String url, Map<String, String> map)
    {
        toMap(url, map, _UTF_8_);
    }
    
    /**
     * 根据URL，生成KEY和VALUE放置到指定的哈希表
     * 
     * @param url       URL
     * @param map       指定哈希表
     * @param encoding  指定编码格式
     */
    public static void toMap(String url, Map<String, String> map, String encoding)
    {
        if (map == null)
            return;
        
        map.putAll(toMap(url, encoding));
    }
    
    /**
     * 根据URL，默认以UTF-8生成KEY和VALUE对应哈希表
     * 
     * @param url       URL
     * @return          生成KEY和VALUE对应哈希表
     */
    public static Map<String, String> toMapUTF8(String url)
    {
        return toMap(url, _UTF_8_);
    }
    
    /**
     * 根据URL，生成KEY和VALUE对应哈希表
     * 
     * @param url       URL
     * @param encoding  编码格式
     * @return          生成KEY和VALUE对应哈希表
     */
    public static Map<String, String> toMap(String url, String encoding)
    {
        Map<String, String> map = new HashMap<String, String>();
        if (Validates.isEmpty(url))
            return map ;

        int i0 = url.indexOf('?');
        if (i0 != -1)
            url = url.substring(i0 + 1);

        int i = url.indexOf('&');
        while (i != -1)
        {
            String keyValue = url.substring(0, i);
            int i1 = keyValue.indexOf('=');
            if (i1 == -1)
                map.put(keyValue, null);
            else
            {
                String key = keyValue.substring(0, i1);
                String value = keyValue.substring(i1 + 1);
                value = decode(value, encoding);
                String mapValue = map.get(key);
                if (mapValue != null)
                    value = mapValue + "," + value;
                map.put(key, value);
            }

            url = url.substring(i + 1);
            i = url.indexOf('&');
        }

        // 还有最后一个
        int i1 = url.indexOf('=');
        if (i1 != -1)
        {
            String key = url.substring(0, i1);
            String value = url.substring(i1 + 1);
            value = decode(value, encoding);
            String mapValue = map.get(key);
            if (mapValue != null)
                value = mapValue + "," + value;
            map.put(key, value);
        }
        
        return map;
    }
    
    /**
     * 根据URL，生成KEY和VALUE对应哈希表，该URL没有作编码处理
     * 
     * @param url   URL
     * @return      生成KEY和VALUE对应哈希表
     */
    public static Map<String, String> toMapNoEncoded(String url)
    {
        Map<String, String> map = new HashMap<String, String>();
        if (Validates.isEmpty(url))
            return map ;

        int i0 = url.indexOf('?');
        if (i0 != -1)
            url = url.substring(i0 + 1);

        int i = url.indexOf('&');
        while (i != -1)
        {
            String keyValue = url.substring(0, i);
            int i1 = keyValue.indexOf('=');
            if (i1 == -1)
                map.put(keyValue, null);
            else
            {
                String key = keyValue.substring(0, i1);
                String value = keyValue.substring(i1 + 1);
                String mapValue = map.get(key);
                if (mapValue != null)
                    value = mapValue + "," + value;
                map.put(key, value);
            }

            url = url.substring(i + 1);
            i = url.indexOf('&');
        }

        // 还有最后一个
        int i1 = url.indexOf('=');
        if (i1 != -1)
        {
            String key = url.substring(0, i1);
            String value = url.substring(i1 + 1);
            String mapValue = map.get(key);
            if (mapValue != null)
                value = mapValue + "," + value;
            map.put(key, value);
        }
        
        return map;
    }
    
    /** 提供两个参数拼接成字符串，不作URLEcoding，用于对参数作签名验签用 */
    public static String toSignString(String key1, Object value1, String key2, Object value2)
    {
        TreeMap<String, Object> treeParamMap = new TreeMap<String, Object>();
        treeParamMap.put(key1, value1);
        treeParamMap.put(key2, value2);
        return toSignString(treeParamMap);
    }
    
    /** 提供三个参数拼接成字符串，不作URLEcoding，用于对参数作签名验签用 */
    public static String toSignString(String key1, Object value1, String key2, Object value2, String key3, Object value3)
    {
        TreeMap<String, Object> treeParamMap = new TreeMap<String, Object>();
        treeParamMap.put(key1, value1);
        treeParamMap.put(key2, value2);
        treeParamMap.put(key3, value3);
        return toSignString(treeParamMap);
    }
    
    /** 
     * 把数组所有元素排序拼接成字符串，不作URLEcoding，并按照"参数=参数值"的模式用"&"字符拼接成字符串，用于对参数作签名验签用
     * 
     * @param paramMap  需要排序并参与字符拼接的参数组
     * @return          拼接后字符串
     */
    public static String toSignString(Map<String, Object> paramMap) 
    {
        TreeMap<String, Object> treeMap = new TreeMap<String, Object>();
        treeMap.putAll(paramMap);
        return toSignString(treeMap);
    }

    /** 
     * 把数组所有元素排序拼接成字符串，不作URLEcoding，并按照"参数=参数值"的模式用"&"字符拼接成字符串，用于对参数作签名验签用
     * 
     * @param paramMap  TreeMap
     * @return          拼接后字符串
     */
    public static String toSignString(TreeMap<String, Object> paramMap) 
    {
        if (paramMap.isEmpty())
            return _EMPTY_;
        
        StringBuilder strb = new StringBuilder();
        for (Entry<String, Object> entry : paramMap.entrySet()) 
        {
            strb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        strb.setLength(strb.length()-1);
        return strb.toString();
    }
    
    /** 
     * 把数组所有元素排序拼接成字符串，不作URLEcoding，并按照"参数=参数值"的模式用"&"字符拼接成字符串，用于对参数作签名验签用
     * 
     * @param paramMap  TreeMap
     * @return          拼接后字符串
     */
    public static String toSignString(TreeMapSS paramMap)
    {
        if (paramMap.isEmpty())
            return _EMPTY_;
        
        StringBuilder strb = new StringBuilder();
        for (Entry<String, String> entry : paramMap.entrySet()) 
        {
            strb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        strb.setLength(strb.length()-1);
        return strb.toString();
    }
    
    /** 提供两个参数拼接成字符串，作URLEncoding */
    public static String toQueryString(String key1, Object value1, String key2, Object value2)
    {
        TreeMap<String, Object> treeParamMap = new TreeMap<String, Object>();
        treeParamMap.put(key1, value1);
        treeParamMap.put(key2, value2);
        return toQueryString(treeParamMap);
    }
    
    /** 提供三个参数拼接成字符串，作URLEncoding */
    public static String toQueryString(String key1, Object value1, String key2, Object value2, String key3, Object value3)
    {
        TreeMap<String, Object> treeParamMap = new TreeMap<String, Object>();
        treeParamMap.put(key1, value1);
        treeParamMap.put(key2, value2);
        treeParamMap.put(key3, value3);
        return toQueryString(treeParamMap);
    }
    
    /** 
     * 把数组所有元素排序，作URLEncoding
     * 
     * @param paramMap  需要排序并参与字符拼接的参数组
     * @return          拼接后字符串
     */
    public static String toQueryString(MapSO paramMap) 
    {
        return toQueryString(paramMap.instance());
    }
    
    /** 
     * 把数组所有元素排序，作URLEncoding
     * 
     * @param paramMap  需要排序并参与字符拼接的参数组
     * @return          拼接后字符串
     */
    public static String toQueryString(Map<String, Object> paramMap) 
    {
        StringBuilder strb = new StringBuilder();
        boolean isFirst = true;
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) 
        {
            String key = entry.getKey();
            String value = encodeUTF8(String.valueOf(entry.getValue()));

            if (!isFirst)
                strb.append("&").append(key).append("=").append(value);
            else
            {
                isFirst = false;
                strb.append(key).append("=").append(value);
            }
        }

        return strb.toString();
    }
    
    /** 
     * 把数组所有元素排序，作URLEncoding
     * 
     * @param paramMap  需要排序并参与字符拼接的参数组
     * @return          拼接后字符串
     */
    public static String toQueryStringString(MapSS paramMap) 
    {
        return toQueryStringString(paramMap.instance());
    }
    
    /** 
     * 把数组所有元素排序，作URLEncoding
     * 
     * @param paramMap  需要排序并参与字符拼接的参数组
     * @return          拼接后字符串
     */
    public static String toQueryStringString(Map<String, String> paramMap) 
    {
        StringBuilder strb = new StringBuilder();
        boolean isFirst = true;
        for (Map.Entry<String, String> entry : paramMap.entrySet()) 
        {
            String key = entry.getKey();
            String value = encodeUTF8(entry.getValue());

            if (!isFirst)
                strb.append("&").append(key).append("=").append(value);
            else
            {
                isFirst = false;
                strb.append(key).append("=").append(value);
            }
        }

        return strb.toString();
    }
    
    /**
     * 给定一个url,和key,取得value
     * 
     * @param url       URL
     * @param key       键值
     * @return          给定一个url,和key,取得value
     */
    public static String get(String url, String key)
    {
        if (Validates.isEmpty(url) || Validates.isEmpty(key))
            return _EMPTY_;

        //统一格式，URL="&aid=fdd&id=ood&idd=dd"，KEY="&id="
        key = "&" + key + "=";
        int p = url.indexOf("?");
        if (p != -1)
            url = url.substring(p+1);
        url = Strings.addStartsWith(url, "&");
        
        int ind = url.indexOf(key);
        if (ind == -1)
            return _EMPTY_;

        int start = ind + key.length();
        int ind2 = url.indexOf('&', start);
        if (ind2 == -1)
            return url.substring(start);
        else
            return url.substring(start, ind2);
    }
    
    /**
     * 给定一个url,和key,取得value
     * 
     * @param url       URL
     * @param key       键值
     * @param encoding  编码
     * @return          给定一个url,和key,取得value
     */
    public static String get(String url, String key, String encoding)
    {
        String value = get(url, key);
        return decode(value, encoding);
    }

    /**
     * 给定一组key,value增加到url中
     * 
     * @param url       URL
     * @param key       键值
     * @param value     值
     * @return          增加后的url
     */
    public static String add(String url, String key, String value)
    {
        if (Validates.isEmptyBlank(url) || Validates.isEmptyBlank(key))
            return Stringx.trim(url);

        if (Validates.isEmptyBlank(value))
            value = _EMPTY_;

        url = url.trim();
        if (url.indexOf("?") == -1)
            url += "?" + key.trim() + "=" + value.trim();
        else
            url += "&" + key.trim() + "=" + value.trim();

        return url;
    }

    /**
     * 给定一组key,value增加到url中
     * 
     * @param url       URL
     * @param key       键值
     * @param value     值
     * @param encoding  编码
     * @return          增加后的url
     */
    public static String add(String url, String key, String value, String encoding)
    {
        value = encode(value, encoding);
        return add(url, key, value);
    }
    
    /**
     * 给定一组key,value修改到url中,注：该方法仅修改第一个，多个不处理
     * 
     * @param url       URL
     * @param key       键值
     * @param value     值
     * @return          修改后的url,注：该方法仅修改第一个，多个不处理
     */
    public static String modify(String url, String key, String value)
    {
        if (Validates.isEmpty(url) || Validates.isEmpty(key))
            return url;

        if (Validates.isEmpty(value))
            value = _EMPTY_;

        int index = url.indexOf(key + "=");
        if (index == -1)
            return add(url, key, value);

        if (index + key.length() == url.length())
            return url += value;

        int valueIndex = url.indexOf('&', index + key.length());
        if (valueIndex == -1)
            return url.substring(0, index + key.length() + 1) + value;

        return url.substring(0, index + key.length() + 1) + value + url.substring(valueIndex);
    }
    
    /**
     * 给定一组key,value修改到url中,注：该方法仅修改第一个，多个不处理
     * 
     * @param url       URL
     * @param key       键值
     * @param value     值
     * @param encoding  编码
     * @return          修改后的url,注：该方法仅修改第一个，多个不处理
     */
    public static String modify(String url, String key, String value, String encoding)
    {
        value = encode(value, encoding);
        return modify(url, key, value);
    }

    /**
     * 给定一组key删除到url中,注：该方法仅删除第一个，多个不处理
     * 
     * @param url       URL
     * @param key       键值
     * @return          删除后的url,注：该方法仅删除第一个，多个不处理
     */
    public static String delete(String url, String key)
    {
        if (Validates.isEmptyBlank(url) || Validates.isEmptyBlank(key))
            return Stringx.trim(url);

        url = url.trim();key=key.trim();
        int index = url.indexOf(key + "=");
        if (index == -1)
            return url;

        // 如果是最后一个，则该KEY的VALUE为空,或最后一个KEY,则删除前一个连接符
        if (index + key.length() == url.length())
            return url.substring(0, index);

        int valueIndex = url.indexOf('&', index + key.length());
        if (valueIndex == -1)
            return url.substring(0, index - 1);

        // 如果不是最后一个,则删除后一个连接符
        return url.substring(0, index) + url.substring(valueIndex + 1);
    }
    
    /**
     * 读取URL内容
     * 
     * @param url       指定URL
     * @param timeout   连接和读流超时时长，单位：秒
     * @return          内容字节数组
     */
    public static HttpResult read(String url, int timeout)
    {
        HttpGetStream conn = new HttpGetStream(url);
        conn.setConnectTimeout(timeout);
        conn.setReadTimeout(timeout);
        conn.execute();
        
        if (conn.getResponseStatus() != 200)
            return new HttpResult(conn.getResponseStatus(), conn.getResponseText());
        
        return new HttpResult(conn.getResponseStatus(), conn.getResponseText(), conn.getBytes());
    }
    
    /**
     * 读取URL内容
     * 
     * @param url           指定URL
     * @param connTimeout   连接超时时长，单位：秒
     * @param readTimeout   读流超时时长，单位：秒
     * @return              内容字节数组
     */
    public static HttpResult read(String url, int connTimeout, int readTimeout)
    {
        HttpGetStream conn = new HttpGetStream(url);
        conn.setConnectTimeout(connTimeout);
        conn.setReadTimeout(readTimeout);
        conn.execute();
        
        if (conn.getResponseStatus() != 200)
            return new HttpResult(conn.getResponseStatus(), conn.getResponseText());
        
        return new HttpResult(conn.getResponseStatus(), conn.getResponseText(), conn.getBytes());
    }
}
